﻿using Microsoft.ClearScript.V8;
using Microsoft.ClearScript;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Script.ClearScript_V8
{
    public delegate void ScriptErrorEventHandler(Exception e, V8ScriptEngine engine);
    public delegate void ScriptLogEventHandler(string log);
    /// <summary>
    /// 脚本管理类
    /// </summary>
    public class ScriptManager : INotifyPropertyChanged
    {
        /// <summary>
        /// 脚本运行出现错误时触发事件
        /// </summary>
        public event ScriptErrorEventHandler OnError;
        /// <summary>
        /// 打印日志时触发事件
        /// </summary>
        public event ScriptLogEventHandler OnLog;
        /// <summary>
        /// 脚本引擎字典
        /// </summary>
        public Dictionary<string, Engine> Engines { get; private set; }
        /// <summary>
        /// 引入脚本的宿主对象集
        /// </summary>
        public Dictionary<string, object> HostObjects { get; private set; }
        /// <summary>
        /// 引入脚本的类型对象集
        /// </summary>
        public Dictionary<string, Type> HostTypes { get; private set; }

        public Data data { get; private set; }

        private CancellationTokenSource cts;

        private int port = 9222;

        private double progress;
        /// <summary>
        /// 脚本加载时同步的进度
        /// </summary>
        public double Progress
        {
            get => progress;
            set
            {
                progress = value;
                NotifyPropertyChanged();
            }
        }
        

        /// <summary>
        /// 初始构造函数, 默认引入data对象
        /// </summary>
        public ScriptManager()
        {
            HostObjects = new Dictionary<string, object>();
            HostTypes = new Dictionary<string, Type>();
            Engines = new Dictionary<string, Engine>();

            //以下为三个脚本默认对象
            data = new Data();
            HostObjects.Add("data", data);
            HostObjects.Add("host", new HostFunctions());
            HostObjects.Add("xHost", new ExtendedHostFunctions());
        }
        
        /// <summary>
        /// 运行脚本
        /// </summary>
        /// <param name="scripts">键: 脚本名, 值: 脚本内容</param>
        public void RunScriptTasks(Dictionary<string, string> scripts)
        {
            if (scripts.Count == 0)
                return;
            if (cts == null)
                cts = new CancellationTokenSource();
            Progress = 0;
            double piece = 100 / (double)scripts.Count;
            foreach (var kvp in scripts)
            {
                V8ScriptEngine v8_engine;
                Engine engine;
                if (!Engines.ContainsKey(kvp.Key))
                {
                    int new_port = PortInUse();
                    v8_engine = new V8ScriptEngine(kvp.Key, V8ScriptEngineFlags.EnableDebugging, new_port)
                    {
                        EnableAutoHostVariables = true
                    };
                    engine = new Engine(v8_engine, new_port);
                    Engines[kvp.Key] = engine;

                }
                else
                {
                    engine = Engines[kvp.Key];
                    v8_engine = engine.ScriptEngine;
                }
                foreach (var obj in HostObjects)
                {
                    v8_engine.AddHostObject(obj.Key, obj.Value);
                }

                foreach (var t in HostTypes)
                {
                    v8_engine.AddHostType(t.Key, t.Value);
                }
                if (engine.Status != RunStatus.Running)
                {
                    try
                    {
                        Task.Run(() =>
                        {
                            Progress += piece;
                            try
                            {
                                engine.Status = RunStatus.Running;
                                OnLog?.Invoke(string.Format(System.Windows.Application.Current.FindResource("script_running") as string, engine.ScriptEngine.Name));
                                v8_engine.Execute(kvp.Key, kvp.Value);
                                OnLog?.Invoke(string.Format(System.Windows.Application.Current.FindResource("script_finished") as string, engine.ScriptEngine.Name));
                                engine.Status = RunStatus.Finished;

                            }
                            catch (Exception e)
                            {
                                engine.Status = RunStatus.Error;
                                OnError?.Invoke(e, v8_engine);
                            }
                        }, cts.Token);
                    }catch(Exception e)
                    {
                        engine.Status = RunStatus.Error;
                        OnError?.Invoke(e, v8_engine);
                    }
                }

            }
        }

        /// <summary>
        /// 运行脚本
        /// </summary>
        /// <param name="name">脚本名</param>
        /// <param name="script">脚本内容</param>
        public void RunScript(string name, string script)
        {

            V8ScriptEngine v8_engine;
            Engine engine;
            if (!Engines.ContainsKey(name))
            {
                int new_port = PortInUse();
                v8_engine = new V8ScriptEngine(name, V8ScriptEngineFlags.EnableDebugging, new_port)
                {
                    EnableAutoHostVariables = true
                };
                engine = new Engine(v8_engine, new_port);
                Engines[name] = engine;
            }
            else
            {
                engine = Engines[name];
                v8_engine = engine.ScriptEngine;
            }
            foreach (var obj in HostObjects)
            {
                v8_engine.AddHostObject(obj.Key, obj.Value);
            }

            foreach (var t in HostTypes)
            {
                v8_engine.AddHostType(t.Key, t.Value);
            }
            if (engine.Status != RunStatus.Running)
            {
                try
                {
                    engine.Status = RunStatus.Running;
                    OnLog?.Invoke(string.Format(System.Windows.Application.Current.FindResource("script_running") as string, engine.ScriptEngine.Name));
                    v8_engine.Execute(name, script);
                    OnLog?.Invoke(string.Format(System.Windows.Application.Current.FindResource("script_finished") as string, engine.ScriptEngine.Name));
                    engine.Status = RunStatus.Finished;
                }
                catch (Exception e)
                {
                    engine.Status = RunStatus.Error;
                    OnError?.Invoke(e, v8_engine);
                }
            }
        }

        /// <summary>
        /// 从文件夹载入脚本
        /// </summary>
        /// <param name="folder_path">文件夹路径</param>
        /// <returns></returns>
        public static Dictionary<string, string> LoadScripts(string folder_path)
        {
            var ss = new Dictionary<string, string>();
            if (Directory.Exists(folder_path))
            {
                var files = Directory.EnumerateFiles(folder_path, "*.js");
                foreach (var file_name in files)
                {
                    ss[Path.GetFileNameWithoutExtension(file_name)] = File.ReadAllText(file_name);
                }
            }
            return ss;
        }

        /// <summary>
        /// 载入类型(字符串形式)到HostTypes
        /// </summary>
        /// <param name="types"></param>
        public void LoadStrTypes(Dictionary<string, string> types)
        {
            foreach(var kvp in types)
            {
                try
                {
                    var strs = kvp.Value.Split(';');
                    var type_name = strs[0];
                    Type type = null;
                    if(strs.Length >= 2)
                    {
                        var file_path = strs[1];
                        if (File.Exists(file_path))
                        {
                            type = Assembly.LoadFrom(file_path).GetType(type_name);
                        }
                    }
                    else
                    {
                        type = Type.GetType(type_name, false, true);
                    }
                    HostTypes[kvp.Key] = type;
                    if(type == null)
                            throw new TypeLoadException();
                }
                catch(Exception e)
                {
                    OnLog?.Invoke(
                        string.Format(System.Windows.Application.Current.FindResource("script_type_load_fail") as string, kvp.Value)
                        + Environment.NewLine + e.ToString());
                }
            }
        }
        
        /// <summary>
        /// 强制停止脚本运行
        /// </summary>
        /// <param name="script_names"></param>
        public void InterruptScripts(params string[] script_names)
        {
            foreach(var name in script_names)
            {
                if(Engines.ContainsKey(name))
                {
                    Engines[name].ScriptEngine.Interrupt();
                    OnLog?.Invoke(string.Format(System.Windows.Application.Current.FindResource("script_stopped") as string, Engines[name].ScriptEngine.Name));
                    Engines[name].Status = RunStatus.None;
                }
            }
        }
        /// <summary>
        /// 停止加载脚本引擎
        /// </summary>
        public void StopLoading()
        {
            OnLog?.Invoke(System.Windows.Application.Current.FindResource("script_stop_all") as string);
            if (cts != null)
            {
                cts.Cancel();
                cts.Dispose();
                cts = null;
            }
        }
        
        private int PortInUse()
        {
            IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();
            IPEndPoint[] ipEndPoints = ipProperties.GetActiveTcpListeners();
            for(;port <= 65535;port++)
            {
                if (!ipEndPoints.Any(endPoint => endPoint.Port == port))
                    break;
            }
            return port;
        }

        public event PropertyChangedEventHandler PropertyChanged;
        // This method is called by the Set accessor of each property.
        // The CallerMemberName attribute that is applied to the optional propertyName
        // parameter causes the property name of the caller to be substituted as an argument.
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
